package repository

import (
	"SmartLibrary/pkg/log"
	"SmartLibrary/pkg/zapgorm2"
	"context"
	"fmt"
	"github.com/glebarez/sqlite"
	"github.com/redis/go-redis/v9"
	"github.com/spf13/viper"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"time"
)

// 用于在上下文中存储事务相关信息
const ctxTxKey = "TxKey"

type Repository struct {
	db *gorm.DB
	//rdb    *redis.Client
	logger *log.Logger
}

func NewRepository(
	logger *log.Logger,
	db *gorm.DB,
	// rdb *redis.Client,
) *Repository {
	return &Repository{
		db: db,
		//rdb:    rdb,
		logger: logger,
	}
}

// Transaction 用于处理事务的接口
type Transaction interface {
	Transaction(ctx context.Context, fn func(ctx context.Context) error) error
}

// NewTransaction 转换类型
func NewTransaction(r *Repository) Transaction {
	return r
}

// DB 用于获取数据库连接
// If you need to create a Transaction, you must call DB(ctx) and Transaction(ctx,fn)
// 如果需要创建 Transaction，必须调用 DB（ctx） 和 Transaction（ctx，fn）
func (r *Repository) DB(ctx context.Context) *gorm.DB {
	v := ctx.Value(ctxTxKey)

	// 上下文对象中有事务信息，就用事务连接，否则就使用原始连接
	if v != nil {
		if tx, ok := v.(*gorm.DB); ok {
			return tx
		}
	}
	return r.db.WithContext(ctx)
}

// Transaction 用于处理事务
func (r *Repository) Transaction(ctx context.Context, fn func(ctx context.Context) error) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		ctx = context.WithValue(ctx, ctxTxKey, tx)
		return fn(ctx)
	})
}

// NewDB 用于创建一个新的数据库连接，根据配置文件中的数据库驱动类型选择相应的数据库连接方式，并配置连接池参数
func NewDB(conf *viper.Viper, l *log.Logger) *gorm.DB {
	var (
		masterDB *gorm.DB
		slaveDB  *gorm.DB
		err      error
	)

	logger := zapgorm2.New(l.Logger)
	masterDbDriver := conf.GetString("data.masterDb.user.driver")
	masterDbDsn := conf.GetString("data.masterDb.user.dsn")
	slaveDbDriver := conf.GetString("data.slaveDb.user.driver")
	slaveDbDsn2 := conf.GetString("data.slaveDb.user.dsn")

	//主库
	switch masterDbDriver {
	case "mysql":
		masterDB, err = gorm.Open(mysql.Open(masterDbDsn), &gorm.Config{
			Logger: logger,
		})
		fmt.Printf("err: %s", err)
	case "postgres":
		masterDB, err = gorm.Open(postgres.New(postgres.Config{
			DSN:                  masterDbDsn,
			PreferSimpleProtocol: true, // disables implicit prepared statement usage
		}), &gorm.Config{})
	case "sqlite":
		masterDB, err = gorm.Open(sqlite.Open(masterDbDsn), &gorm.Config{})
	default:
		panic("unknown db driver")
	}
	//从库
	switch slaveDbDriver {
	case "mysql":
		slaveDB, err = gorm.Open(mysql.Open(slaveDbDsn2), &gorm.Config{
			Logger: logger,
		})
		fmt.Printf("err: %s", err)
	case "postgres":
		slaveDB, err = gorm.Open(postgres.New(postgres.Config{
			DSN:                  slaveDbDsn2,
			PreferSimpleProtocol: true, // disables implicit prepared statement usage
		}), &gorm.Config{})
	case "sqlite":
		slaveDB, err = gorm.Open(sqlite.Open(slaveDbDsn2), &gorm.Config{})
	default:
		panic("unknown db driver")
	}
	if err != nil {
		panic(err)
	}
	masterDB = masterDB.Debug()
	slaveDB = slaveDB.Debug()

	// Connection Pool config
	//主库
	sqlMasterDB, err := masterDB.DB()
	if err != nil {
		panic(err)
	}
	sqlMasterDB.SetMaxIdleConns(10)
	sqlMasterDB.SetMaxOpenConns(100)
	sqlMasterDB.SetConnMaxLifetime(time.Hour)
	//从库
	sqlSlaveDB, err := slaveDB.DB()
	if err != nil {
		panic(err)
	}
	sqlSlaveDB.SetMaxIdleConns(10)
	sqlSlaveDB.SetMaxOpenConns(100)
	sqlSlaveDB.SetConnMaxLifetime(time.Hour)

	go func() {
		for {
			if !checkMasterDBHealth(masterDB) {
				// 切换到备用数据库
				switchToSlaveDB(masterDB, slaveDB, l)
			}
			time.Sleep(5 * time.Second)
		}
	}()

	return masterDB
}

// NewRedis 用于创建一个新的 Redis 连接
func NewRedis(conf *viper.Viper) *redis.Client {
	rdb := redis.NewClient(&redis.Options{
		Addr:     conf.GetString("data.redis.addr"),
		Password: conf.GetString("data.redis.password"),
		DB:       conf.GetInt("data.redis.db"),
	})

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	_, err := rdb.Ping(ctx).Result()
	if err != nil {
		panic(fmt.Sprintf("redis error: %s", err.Error()))
	}

	return rdb
}

func checkMasterDBHealth(masterDB *gorm.DB) bool {
	err := masterDB.Raw("SELECT 1").Error
	if err != nil {
		fmt.Println("logger", "masterDB error: ", err)
	}
	return err == nil
}

func switchToSlaveDB(masterDB *gorm.DB, slaveDB *gorm.DB, l *log.Logger) {
	l.Logger.Info("Switching to slave database")
	// 在此执行从数据库的查询或者写入操作
	masterDB, slaveDB = slaveDB, masterDB
}
